2017/01/11

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

おそらく golang を暫く使っておられる方であればご存じだと思いますが今日は crypto/ssh を紹介します。

Windows で ssh と聞くとどうしても msys やら cygwin やら入れないといけなくて

  • ランタイムを入れるのが嫌だ
  • 特殊なパス形式とか嫌だ
  • そもそも業務で使いづらい

といった個人的もしくは政治的な事柄が起きてなかなか実現しづらかったりします。でも golang なら msys や cygwin に頼らず ssh コマンドを、しかもライブラリとして扱う事が出来るので golang で作ったウェブサーバやバッチから UNIX ホストに対して ssh コマンドを送る事が出来るのです。

ssh - GoDoc

package ssh import "golang.org/x/crypto/ssh" Package ssh implements an SSH client and server. SSH is...

https://godoc.org/golang.org/x/crypto/ssh

しかも openssh に依存していないので、openssh の実装に脆弱性が発見されたとしても影響を受けません。インタフェースも netos/exec がうまく組み合わさったイメージで扱う事が出来て非常に便利かつ拡張性のあるパッケージになっています。どれくらい簡単で拡張性が高いかを分かって頂ける様にオレオレ ssh コマンドを作ってみました。通常 ssh コマンドはユーザ名、ホストおよびオプションを指定して ssh コマンドを起動し、パスワードプロンプトにパスワード(パスフレーズ)を入力してログインしますが、この例ではパスワードをコマンド引数から得られる様にしてあります。

package main

import (
    "flag"
    "fmt"
    "os"
    "strings"
    "time"

    "golang.org/x/crypto/ssh"
)

var (
    user     = flag.String("u""""user")
    password = flag.String("p""""password")
    port     = flag.Int("P"22"port")
)

func run() int {
    flag.Parse()
    if flag.NArg() == 0 {
        flag.Usage()
        return 2
    }

    config := &ssh.ClientConfig{
        User: *user,
        Auth: []ssh.AuthMethod{
            ssh.Password(*password),
        },
        Timeout:         5 * time.Second,
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    hostport := fmt.Sprintf("%s:%d", flag.Arg(0), *port)
    conn, err := ssh.Dial("tcp", hostport, config)
    if err != nil {
        fmt.Fprintf(os.Stderr, "cannot connect %v%v", hostport, err)
        return 1
    }
    defer conn.Close()

    session, err := conn.NewSession()
    if err != nil {
        fmt.Fprintf(os.Stderr, "cannot open new session: %v", err)
        return 1
    }
    defer session.Close()

    go func() {
        time.Sleep(5 * time.Second)
        conn.Close()
    }()

    session.Stdout = os.Stdout
    session.Stderr = os.Stderr
    session.Stdin = os.Stdin
    err = session.Run(strings.Join(flag.Args()[1:], " "))
    if err != nil {
        fmt.Fprint(os.Stderr, err)
        if ee, ok := err.(*ssh.ExitError); ok {
            return ee.ExitStatus()
        }
        return 1
    }
    return 0
}

func main() {
    os.Exit(run())
}

os/exec.Command と同じ様に os.Stdout や os.Stderr をパイプ出来る様になっていて、終了コードも得られる様になっています。簡単ですね。もう少しコードを足せば公開鍵認証を行う事も出来ます。詳しくはドキュメントを参照して下さい。サーバがパスワード認証をサポートしている場合にはバッチコマンドとして実行出来るので、もしかすると意外と便利かもしれません。ただしパスワードがコマンド引数になるという事は、ps コマンドで他のユーザにパスワードが漏れてしまう危険性がある事を理解しておいて下さい。

Posted at by